package particle;

import java.util.ArrayList;

import autonomous.FlockBoid;
import processing.core.PApplet;
import processing.core.PVector;

public class Particle extends PApplet {
	PVector loc;
	PVector vel;
	PVector acc;
	float r;
	float timer;
	float maxspeed;
	float maxforce; // Maximum steering force
	float bounce = (float) 0.7;

	PApplet parent; // The parent PApplet that we will render ourselves onto

	// // One constructor
	// Particle(PApplet p, PVector a, PVector v, PVector l, float r_) {
	// parent = p;
	// acc = a.get();
	// vel = v.get();
	// loc = l.get();
	// r = r_;
	// timer = (float) 100.0;
	// }

	// Another constructor (the one we are using here)
	Particle(PApplet p, PVector l) {
		parent = p;
		// Another constructor (the one we are using here)
		maxspeed = 8;
		// Boring example with constant acceleration
		acc = new PVector(0, 0, 0);
		vel = new PVector(random(-5, 5), random(-5, 5), 0);
		loc = l.get();
		r = (float) 25.0;
		timer = (float) 220.0;
	}

	public void run() {
		update();
		render();
		borders();
	}

	public void swarm(ArrayList particles) {
		PVector sep = separate(particles); // Separation
		PVector ali = align(particles); // Alignment
		PVector coh = cohesion(particles); // Cohesion
		// Arbitrarily weight these forces
		sep.mult((float) 1.5);
		ali.mult((float) 1.0);
		coh.mult((float) 1.0);
		// Add the force vectors to acceleration
		acc.add(sep);
		acc.add(ali);
		acc.add(coh);
	}

	// Method to update location
	void update() {
		vel.add(acc);
		vel.limit(maxspeed);
		loc.add(vel);
		acc.mult(0);
		timer -= 0.5;
	}

	public void applyForce(PVector force) {
		float mass = 10; // We aren't bothering with mass here
		force.div(mass);
		acc.add(force);
	}

	PVector steer(PVector target, boolean slowdown) {
		PVector steer; // The steering vector
		PVector desired = PVector.sub(target, loc); // A vector pointing from
													// the location to the
													// target
		float d = desired.mag(); // Distance from the target is the magnitude of
									// the vector
		// If the distance is greater than 0, calc steering (otherwise return
		// zero vector)
		if (d > 0) {
			// Normalize desired
			desired.normalize();
			// Two options for desired vector magnitude (1 -- based on distance,
			// 2 -- maxspeed)
			if ((slowdown) && (d < 100.0f))
				desired.mult(maxspeed * (d / 100.0f)); // This damping is
														// somewhat arbitrary
			else
				desired.mult(maxspeed);
			// Steering = Desired minus Velocity
			steer = PVector.sub(desired, vel);
			steer.limit(maxforce); // Limit to maximum steering force
		} else {
			steer = new PVector(0, 0);
		}
		return steer;
	}

	PVector separate(ArrayList particles) {
		float desiredseparation = 25.0f;
		PVector steer = new PVector(0, 0, 0);
		int count = 0;
		// For every boid in the system, check if it's too close
		for (int i = 0; i < particles.size(); i++) {
			Particle other = (Particle) particles.get(i);
			float d = PVector.dist(loc, other.loc);
			// If the distance is greater than 0 and less than an arbitrary
			// amount (0 when you are yourself)
			if ((d > 0) && (d < desiredseparation)) {
				// Calculate vector pointing away from neighbor
				PVector diff = PVector.sub(loc, other.loc);
				diff.normalize();
				diff.div(d); // Weight by distance
				steer.add(diff);
				count++; // Keep track of how many
			}
		}
		// Average -- divide by how many
		if (count > 0) {
			steer.div((float) count);
		}

		// As long as the vector is greater than 0
		if (steer.mag() > 0) {
			// Implement Reynolds: Steering = Desired - Velocity
			steer.normalize();
			steer.mult(maxspeed);
			steer.sub(vel);
			steer.limit(maxforce);
		}
		return steer;
	}

	//
	// // Alignment
	// // For every nearby boid in the system, calculate the average velocity
	PVector align(ArrayList particles) {
		float neighbordist = (float) 50.0;
		PVector steer = new PVector(0, 0, 0);
		int count = 0;
		for (int i = 0; i < particles.size(); i++) {
			Particle other = (Particle) particles.get(i);
			float d = PVector.dist(loc, other.loc);
			if ((d > 0) && (d < neighbordist)) {
				steer.add(other.vel);
				count++;
			}
		}
		if (count > 0) {
			steer.div((float) count);
		}

		// As long as the vector is greater than 0
		if (steer.mag() > 0) {
			// Implement Reynolds: Steering = Desired - Velocity
			steer.normalize();
			steer.mult(maxspeed);
			steer.sub(vel);
			steer.limit(maxforce);
		}
		return steer;
	}

	PVector cohesion(ArrayList particles) {
		float neighbordist = (float) 50.0;
		PVector sum = new PVector(0, 0, 0); // Start with empty vector to
		// accumulate all locations
		int count = 0;
		for (int i = 0; i < particles.size(); i++) {
			Particle other = (Particle) particles.get(i);
			float d = PVector.dist(loc, other.loc);
			if ((d > 0) && (d < neighbordist)) {
				sum.add(other.loc); // Add location
				count++;
			}
		}
		if (count > 0) {
			sum.div((float) count);
			return steer(sum, false); // Steer towards the location
		}
		return sum;
	}

	// Method to display
	void render() {
		parent.ellipseMode(CENTER);
		parent.stroke(0, timer);
		parent.fill(0, timer);
		parent.ellipse(loc.x, loc.y, r, r);
	}

	void borders() {
		if (loc.y > parent.height) {
			vel.y *= -bounce;
			loc.y = parent.height;
		} else if (loc.y < 0) {
			vel.y *= -bounce;
			loc.y = 0;
		}
		if (loc.x > parent.width) {
			vel.x *= -bounce;
			loc.x = parent.width;
		} else if (loc.x < 0) {
			vel.x *= -bounce;
			loc.x = 0;
		}
	}

	// Is the particle still useful?
	boolean dead() {
		if (timer <= 0.0) {
			return true;
		} else {
			return false;
		}
	}
}
